﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.UI;
using Emgu.Util;

namespace Virtual_Mouse
{
    class CameraController
    {
        private Capture capture;
        private ImageViewer viewer;

        public Image<Bgr, byte> image;
        public Image<Bgr, byte> background;

        private MouseSimulator mouse;

        private Seq<Point> bound;
        private MCvBox2D boundingRectangle;
        private Seq<Point> filteredBound;
        private MCvConvexityDefect[] defectArray;
        private Seq<MCvConvexityDefect> defects;
        private bool clicked;
        private int clickCounter;
         
        public CameraController()
        {
            try
            {
                //capture = new Capture(@"..\..\data\HAND.MPG");
                capture = new Capture(0);
                viewer = new ImageViewer();
                background = capture.QueryFrame();
                clicked = false;

                mouse = new MouseSimulator();

                Application.Idle += new EventHandler(FrameHandle);
                this.viewer.Disposed += ((sender, args) => Application.Exit());

                // dodanie eventu wciskania klawiszy i odblokowanie go
                this.viewer.KeyDown += ViewerOnKeyDown;
                this.viewer.KeyPreview = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }



        public void Start()
        {
            this.viewer.Show();
        }

        private void FrameHandle(object sender, EventArgs e)
        {
                image = capture.QueryFrame();
                //image._EqualizeHist();
                //var black = DetectSkin(image, new Hsv(0.0, 23.0, 0.0), new Hsv(50.0, 60.0, 255.0));

                Image<Gray, byte> black = DetectSkin(image, new Ycc(0, 131, 80), new Ycc(255, 185, 135));
                Contour<Point> hand = DetectHand(black);

                DrawHand(black, hand);
        }

        private void DrawHand(Image<Gray, byte> black, Contour<Point> hand)
        {
            if (hand == null)
            {
                viewer.Image = black;
            }
            else
            {
                boundingRectangle = hand.GetMinAreaRect();
                bound = hand.GetConvexHull(Emgu.CV.CvEnum.ORIENTATION.CV_CLOCKWISE);

                using (MemStorage storage = new MemStorage())
                {
                    Contour<Point> tempHand = hand.ApproxPoly(hand.Perimeter * 0.00001, storage);

                    defects = tempHand.GetConvexityDefacts(storage, Emgu.CV.CvEnum.ORIENTATION.CV_CLOCKWISE);

                    defectArray = defects.ToArray();

                    DrawFingers();
                }


                image.DrawPolyline(bound.ToArray(), true, new Bgr(180, 100, 60), 2);
                image.Draw(hand, new Bgr(100, 180, 220), 2);
                image.Draw(new Cross2DF(new PointF(boundingRectangle.center.X, boundingRectangle.center.Y), 10, 10),
                           new Bgr(0, 0, 255), 2);

                MoveCoursor();
                viewer.Image = image;
            }
        }

        private void MoveCoursor()
        {
            int a = (int) boundingRectangle.center.X;
            int b = (int) boundingRectangle.center.Y;

            mouse.MoveCursor(new Point(a, b), new Point(image.Width, image.Height));

            MCvFont font = new MCvFont(Emgu.CV.CvEnum.FONT.CV_FONT_HERSHEY_COMPLEX, 1d, 1d);

            // debug
            image.Draw(a.ToString(), ref font, new Point(10, 40), new Bgr(Color.Azure));
            image.Draw(b.ToString(), ref font, new Point(10, 70), new Bgr(Color.Azure));
        }

        private void DrawFingers()
        {
            int fingerNumber = 0;

            for (int i = 0; i < defects.Total; i++)
            {
                PointF startPoint = new PointF((float)defectArray[i].StartPoint.X,
                                                (float)defectArray[i].StartPoint.Y);
                PointF depthPoint = new PointF((float)defectArray[i].DepthPoint.X,
                                                (float)defectArray[i].DepthPoint.Y);
                PointF endPoint = new PointF((float)defectArray[i].EndPoint.X,
                                                (float)defectArray[i].EndPoint.Y);

                LineSegment2D startDepthLine = new LineSegment2D(defectArray[i].StartPoint, defectArray[i].DepthPoint);
                LineSegment2D endDepthLine = new LineSegment2D(defectArray[i].DepthPoint, defectArray[i].EndPoint);

                CircleF startCircle = new CircleF(startPoint, 5f);
                CircleF depthCircle = new CircleF(depthPoint, 5f);
                CircleF endCircle = new CircleF(endPoint, 5f);


                if (startPoint.Y + 5 < depthPoint.Y && endPoint.Y + 5 < depthPoint.Y)
                {
                    fingerNumber++;
                    image.Draw(startDepthLine, new Bgr(Color.Green), 2);
                    image.Draw(endDepthLine, new Bgr(Color.Green), 2);

                    image.Draw(startCircle, new Bgr(Color.Red), 2);
                    image.Draw(depthCircle, new Bgr(Color.Yellow), 5);
                    image.Draw(endCircle, new Bgr(Color.Blue), 4);
                }
            }

            // 4 dolki czyli 5 palcow xD
            fingerNumber++;

            MCvFont font = new MCvFont(Emgu.CV.CvEnum.FONT.CV_FONT_HERSHEY_COMPLEX, 1d, 1d);
            image.Draw("Nr." + fingerNumber.ToString(), ref font, new Point(10, 100), new Bgr(Color.Azure));

            click(fingerNumber);
        }

        private void click(int fingerNumber)
        {
            if (fingerNumber < 3)
            {
                clickCounter++;

                if ((clickCounter > 10) && (clicked == false))
                {
                    mouse.ClickLeftMouseButton();
                    clicked = true;
                }
            }
            else
            {
                // a tu jego puszczenie
                clicked = false;
                clickCounter = 0;
            }
        }


        /// <summary>
        /// Wykrywanie ręki jako najwięszkego konturu na obrazie.
        /// </summary>
        /// <param name="img">Obraz czarnobialy (poddany wczesniejszemu wykrywaniu skory</param>
        /// <returns>Kontur ręki (największy kontur występujący na obrazie)</returns>
        private Contour<Point> DetectHand(Image<Gray, byte> img)
        {
            Contour<Point> hand = null;
            Double max = 0;
            for (Contour<Point> currentCountur = img.FindContours(); currentCountur != null; currentCountur = currentCountur.HNext)
            {
                if (currentCountur.Perimeter > max)
                {
                    max = currentCountur.Perimeter;
                    hand = currentCountur;
                }
            }
            return hand;
        }

        /// <summary>
        /// Wykrywanie skóry na obrazie oraz lekka jego poprawa. 
        /// Skóra wykrywana jest jako kolory z danego zakresu.
        /// </summary>
        /// <param name="img">Obraz źródłowy</param>
        /// <param name="min">Minimalne kolory zaliczane do skóry</param>
        /// <param name="max">Maksymalne kolory zaliczane do skóry</param>
        /// <returns>Obraz czarnobiały, skóra zaznaczona na biało, tło na czarno</returns>
        public Image<Gray, byte> DetectSkin(Image<Bgr, byte> img, IColor min, IColor max)
        {
            var currentYCrCbFrame = img.Convert<Ycc, Byte>();
            var skin = new Image<Gray, byte>(img.Width, img.Height);
            skin = currentYCrCbFrame.InRange((Ycc)min, (Ycc)max);
            
            var rect_12 = new StructuringElementEx(12, 12, 6, 6, Emgu.CV.CvEnum.CV_ELEMENT_SHAPE.CV_SHAPE_RECT);
            CvInvoke.cvErode(skin, skin, rect_12, 1);
            
            var rect_6 = new StructuringElementEx(6, 6, 3, 3, Emgu.CV.CvEnum.CV_ELEMENT_SHAPE.CV_SHAPE_RECT);
            CvInvoke.cvDilate(skin, skin, rect_6, 2);
            
            return skin;
        }

        private void ViewerOnKeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                Application.Exit();
            }
        }
    }
}
